﻿using System;
using System.IO;
using System.Collections.Generic;
using ASquared.SymbolicMath; 

namespace ASquared.ModelOptimization
{
    public class LinearModel
    {
        private Matrix _weights;
        private String[] _varNames;
        private SymbolicMatrix _vars, _tvars;
        private Symbol _fxn;
        private Matrix _means, _stdevs, _tmeans, _tstdevs; 

        // Constructors
        public LinearModel(Matrix weights, SymbolicMatrix Vars, Matrix means, Matrix stdevs, SymbolicMatrix targetVars, Matrix targetMeans, Matrix targetStdevs)
        {
            _weights = weights; 
            _vars = Vars;
            _varNames = Array.ConvertAll(_vars.To1DArray(), var => var.ToString());
            _fxn = ((SymbolicMatrix)_weights).Mult(_vars, TransposeOption.EitherAsNeeded)[0];
            _means = means.ToRow();
            _stdevs = stdevs.ToRow();
            _tvars = targetVars; 
            _tmeans = targetMeans;
            _tstdevs = targetStdevs; 
        }

        // Properties
        public SymbolicMatrix Variables
        {
            get
            {
                return _vars;
            }
        }

        public String[] VariableNames
        {
            get
            {
                return _varNames; 
            }
        }

        public SymbolicMatrix TargetVariables
        {
            get
            {
                return _tvars;
            }
        }

        public Matrix Weights
        {
            get
            {
                return _weights; 
            }
        }

        public Matrix Means
        {
            get
            {
                return _means; 
            }
        }

        public Matrix StandardDeviations
        {
            get
            {
                return _stdevs; 
            }
        }

        public Matrix TargetMeans
        {
            get
            {
                return _tmeans; 
            }
        }

        public Matrix TargetStandardDeviations
        {
            get
            {
                return _tstdevs;
            }
        }

        public Symbol Function
        {
            get
            {
                return _fxn; 
            }
        }

        // Methods 
        public Matrix Evaluate(Matrix X, Boolean AreStandardized)
        {
            Matrix XStd;
            Matrix ones = new Matrix(X.NumOfRows, 1, 1.0);
            if (!AreStandardized)
                XStd = (X - ones * _means).ElementwiseDivide(ones * _stdevs); 
            else
                XStd = X;
            Matrix YStd = XStd * _weights;
            return YStd.ElementwiseMult(ones * _tstdevs) + (ones * _tmeans);
        }

        public SymbolicMatrix Substitute(SymbolicMatrix X, Boolean AreStandardized)
        {
            SymbolicMatrix XStd;
            Matrix ones = new Matrix(X.NumberOfRows, 1, 1.0);
            if (!AreStandardized)
                XStd = (X - ones * _means).ElementwiseDivide(ones * _stdevs);
            else
                XStd = X;
            SymbolicMatrix YStd = XStd * _weights;
            return YStd.ElementwiseMult(ones * _tstdevs) + (SymbolicMatrix)(ones * _tmeans);
        }

        public static LinearModel FromFile(string csvfile)
        {
            // Read file into array
            StreamReader sr = new StreamReader(csvfile);
            String[] lines = sr.ReadToEnd().Split(new Char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); 
            sr.Close(); 

            // Loop through array 
            List<Double> weights = new List<Double>(lines.Length);
            List<Symbol> variables = new List<Symbol>(lines.Length);
            List<Double> means = new List<Double>(lines.Length);
            List<Double> stdevs = new List<Double>(lines.Length);
            List<Symbol> targetVars = new List<Symbol>(1);
            List<Double> targetMeans = new List<Double>(1);
            List<Double> targetStdevs = new List<Double>(1);
            Char[] delim = new Char[] { ',' };
            for (Int32 i = 0; i < lines.Length; i++) 
            {
                lines[i] = lines[i].Trim();
                if (lines[i].Equals(""))
                    continue;

                String[] line = lines[i].Split(delim, StringSplitOptions.RemoveEmptyEntries);
                Symbol var; 
                if (lines[i].ToLower().StartsWith("targetvar"))
                {
                    targetVars.Add(var = line[1].Trim());
                    targetMeans.Add(Convert.ToDouble(line[2].Trim()));
                    targetStdevs.Add(Convert.ToDouble(line[3].Trim())); 
                }
                else
                {
                    variables.Add(var = line[0].Trim());
                    weights.Add(Convert.ToDouble(line[1].Trim()));
                    means.Add(Convert.ToDouble(line[2].Trim()));
                    stdevs.Add(Convert.ToDouble(line[3].Trim()));
                }
            }

            return new LinearModel((Matrix)weights.ToArray(), 
                (SymbolicMatrix)variables.ToArray(), (Matrix)means.ToArray(), (Matrix)stdevs.ToArray(), 
                (SymbolicMatrix)targetVars.ToArray(), (Matrix)targetMeans.ToArray(), (Matrix)targetStdevs.ToArray()); 
        }
    }
}
